class_name GameplayEffect
extends RefCounted
# 由于效果可以堆叠，堆叠分为两种：
# 1. 按效果施加者堆叠，即不同的施加者所施加的效果分开堆叠，例如敌人 A 和 B 同时对玩家施加标记效果，虽然标记效果的资源是相同的，但这是两个不同的效果，即分开堆叠。
# 2. 按效果持有者堆叠，即不同的施加者所施加的效果一起堆叠，例如敌人 A 和 B 同时对玩家施加中毒效果，此时玩家身上会存在一个堆叠数为 2 的中毒效果。
#
# 首先需要明确一些设计：
# 1. 效果能不能有等级？如果有，那么一级的效果能不能和三级的效果堆叠？
# 2. 按效果持有者堆叠时，敌人 A 和 B 分别给玩家叠了三层和七层中毒，玩家此时应当整体算为十层中毒，那造成的伤害怎么溯源（伤害三七分还是都按十层的数值溯源）？
# 因此，我需要给出自己的设计前提：
# 1. 效果不能有等级，部分功能由堆叠数或者组件实现，如果要区分不同强度的中毒效果，则使用不同的效果资源，即低级中毒、中级中毒、高级中毒等。
# 2. 如果效果的数据计算是线性的，其实可以三七分溯源，但是这种方法应用到非线性的堆叠数计算中时，数值会不大准确，不过我还是在此处按堆叠数使用均分的方法。
#
# 这里多补充一个点，就是能力有 AbilityAsset、Ability、AbilitySpec，而效果只有 EffectAsset、Effect 的设计，我自己的理解是：
# 1. 能力有 AbilityAsset 是合情合理的，因为可以配置通用能力的名称、描述、最大等级等资源。
# 2. 游戏角色可以拥有能力，此时可以确定能力的持有者 owner，可以自由地升级或者使用自己的能力，因此由 AbilityAsset 派生出角色自己的能力 Ability。
# 3. 能力自然是可以使用的，当玩家使用能力时才会确定能力的目标 target，即对谁造成了伤害等，因此由 Ability 派生出正在使用的能力 AbilitySpec。
# 对于效果，自然也会有 EffectAsset，但是是否需要 EffectSpec 呢？答案是否定的。
# 虽然效果也有自己的等级，但是效果没有和能力一般的持有者，效果的施加者和持有者是同时可以确定的，不如说如果效果其实可以不记录施加者，只需要持有者就够了。
#
# 接下来讨论一下堆叠、持续、周期的组合：
# 1. 存在多层中毒，当持续时间结束后，多层中毒一同消失。
# 2. 存在多层中毒，当持续时间结束后，仅增加或者减少一层中毒，然后重置持续时间。（不作为周期性效果，仅是发生变化的持续性效果）
# 3. 存在多层中毒，每层中毒的持续时间相互独立。

## 效果是否激活
var active: bool

## 效果是否过期
var expired: bool

## 效果脏位
var dirty: bool

## 效果资源
var effect_asset: GameplayEffectAsset

## 效果施加者（当按照持有者堆叠时，效果可以有多个施加者）
var sources: Array[Variant]

## 效果持有者
var target: Variant

## 效果组件
var components: Dictionary


func _init(
	init_effect_asset: GameplayEffectAsset,
	init_target: Variant,
) -> void:
	active = true
	expired = false
	dirty = false
	effect_asset = init_effect_asset
	sources = []
	target = init_target
	for component_asset: GameplayEffectComponentAsset in effect_asset.component_assets:
		var component_name: String = component_asset.get_component_name()
		if components.has(component_name):
			Logger.error("Already has this effect component with given asset '%s'." % component_name)
			continue
		else:
			var component = component_asset.create_spec()
			component.parent = self
			components[component_name] = component


func get_component(type_name: String) -> GameplayEffectComponent:
	return components.get(type_name)


func on_attach(source: Variant) -> void:
	if not sources.has(source):
		sources.append(source)

	for component: GameplayEffectComponent in components.values():
		component.on_attach(source)


func on_update(delta: float) -> void:
	if not expired and active:
		for component: GameplayEffectComponent in components.values():
			component.on_update(delta)


func on_periodic_tick() -> void:
	if not expired and active:
		for component: GameplayEffectComponent in components.values():
			component.on_periodic_tick()


func on_duration_end() -> void:
	if not expired and active:
		for component: GameplayEffectComponent in components.values():
			component.on_duration_end()


func on_stack_update(variation: int) -> void:
	dirty = true
	for component: GameplayEffectComponent in components.values():
		component.on_stack_update(variation)


func on_detach(source: Variant) -> void:
	if sources.has(source):
		for component: GameplayEffectComponent in components.values():
			component.on_detach(source)
		sources.erase(source)
	else:
		Logger.error("Invalid source object.")


func apply_effect() -> void:
	if expired or not active:
		return

	var effect_modifiers = {
		"effect": self,
		"modifiers": [],
	}
	
	for source: Variant in sources:
		effect_modifiers["modifiers"].append({
			"operation": effect_asset.modifier_asset.operation,
			"source": source,
			"magnitude": effect_asset.modifier_asset.get_magnitude(self, source, target),
		})
	
	target.attribute_manager.apply_modifiers(effect_modifiers)
